package com.demo.util.id;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;

import com.demo.service.TableJnlnoService;

/**
 *	基于流水序号表的流水号生产工厂【已测试】
 * @author ytzhu
 *
 */
public class TableIdFactory implements InitializingBean{
	
	private static final Log log = LogFactory.getLog(TableIdFactory.class) ;
	
	/**
	 *	流水序号类型
	 */
	private String jnlType ;
	/**
	 *	当前获取流水序号
	 */
	private long currentId ;
	/**
	 *	当前缓存最大序号
	 */
	private long maxCacheId ;
	/**
	 *	负载因子，默认0.75，
	 *	当前流水号占比达到负载因子时，就会异步预申请新的序号段，减少本地缓存序号耗尽时才申请序号段造成的请求线程阻塞的可能性。
	 */
	private float loadFactor = 0.75f ;
	/**
	 *	步长
	 */
	private int step = 1000;
	/**
	 *	流水号前缀
	 */
	private String prefix = "" ;
	/**
	 *	流水序号位长度
	 */
	private int length = 9;
	
	/**
	 *	数据库操作对象
	 */
	private TableJnlnoService tableJnlnoService ;
	/**
	 *	尝试次数（针对数据库查询失败时）
	 */
	private int tryTimes = 3 ;
	/**
	 *	获取流水号超时时间，默认3s
	 */
	private long generateTimeout = 3000L ;
	/**
	 *	预申请流水号超时时间，默认10s
	 */
	private long preApplytimeout = 10000L ;
	/**
	 *	获取流水号的可重入锁
	 */
	private Lock generateLock = new ReentrantLock() ;
	/**
	 *	预申请流水号的可重入锁
	 */
	private Lock preApplyLock = new ReentrantLock() ;
	/**
	 *	预申请流水号的异步结果
	 */
	private AtomicReference<Future<Long>> preApplyFuture = new AtomicReference<Future<Long>>() ;
	/**
	 *	预申请流水号使用的异步线程池（固定为1个线程）
	 */
	private ExecutorService preApplyExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS
			, new SynchronousQueue<>(), new SequenceThreadFactory(), new ThreadPoolExecutor.DiscardPolicy() ) ;

	/**
	 *	生成新的流水号
	 * @return
	 */
	public String generate() {
		// 1.先获取“生成锁”，得到锁后再生成流水号（避免多线程之间序号冲突）
		boolean locked ;
		try {
			locked = this.generateLock.tryLock(this.generateTimeout, TimeUnit.MILLISECONDS) ;
		} catch (InterruptedException e) {
			throw new RuntimeException("fatal error in SequenceIdFactory, interrupted") ;
		}
		if(!locked) {
			log.error("timeout generating new sequence id, over time: "+ this.generateTimeout +" ms!");
            throw new RuntimeException("fatal error in SequenceIdFactory, timed out");
		}
		
		try {
		// 2. 生成流水号
			this.currentId ++ ;
			
			// 2.1 若使用占比达到负载因子，则进行预申请
			if(this.maxCacheId - this.currentId < this.step * (1 - this.loadFactor)) {
				this.asyncPreApplyMaxCacheId();
			}
			
			// 2.2 若超过缓存最大序号，则从数据库重新获取最大缓存值
			if(this.currentId >= this.maxCacheId) {
				try {
					// 若当前没有异步预申请，则现场触发一次
					if(this.preApplyFuture.get() == null) {
						this.asyncPreApplyMaxCacheId(); 
					}
					// 等待异步查询数据库的返回结果（设置申请超时时间），更新最大缓存序号和当前序号
					this.maxCacheId = this.preApplyFuture.get().get(this.preApplytimeout, TimeUnit.MILLISECONDS) ;
					this.currentId = this.maxCacheId - this.step ;
					log.info("IdFactory updated, new maxCacheId: "+this.maxCacheId+", currentId: "+this.currentId);
				}catch(Exception e) {
					log.error("update new maxCacheId failed with: ", e);
					throw new RuntimeException("fatal error in SequenceIdFactory, pre apply error");
				}finally {
					// 取消未完成的异步预申请
					if(this.preApplyFuture.get() != null) {
						if(!this.preApplyFuture.get().isCancelled() && !this.preApplyFuture.get().isDone()) {
							log.warn("canceling unfinished pre apply task due to timeout or error occurred!");
							this.preApplyFuture.get().cancel(true) ;
						}
						this.preApplyFuture.set(null) ;
					}
				}
				
			}
			
			// 2.3 根据序号位长度，不足的前面补0
			String str = Long.toString(this.currentId) ;
			StringBuilder sb = new StringBuilder() ;
			for(int i = 0 ; i < this.length - str.length() ; i++) {
				sb.append("0") ;
			}
			
			// 2.4 返回最终的流水号
			return this.prefix + sb.toString() + str ;
			
		}finally {
		// 3. 释放“生成锁”
			if(locked) {
				this.generateLock.unlock();
			}
		}
	
	}
	
	/**
	 *	异步预申请新的流水号段
	 */
	private void asyncPreApplyMaxCacheId() {
		// 若有异步结果返回，则不再重复申请
		if(this.preApplyFuture.get() != null) {
			return ;
		}
		// 先获取“预申请锁”，得到锁后再进行预申请
		if(!this.preApplyLock.tryLock()) {
			return ;
		}
		
		// 获取到“预申请锁”后，进行异步预申请
		try {
			if(this.preApplyFuture.get() == null) {
				this.preApplyFuture.set(this.preApplyExecutor.submit(
						new Callable<Long>() {
							@Override
							public Long call() throws Exception {
								// 查询数据库Sequence，获取最新序号
								long newMaxCacheId = getNextMaxCacheIdFromDbRetry() ;
								log.debug("maxCacheId usage over "+(loadFactor * 100)+",new maxCacheId applied:"+newMaxCacheId);
								return newMaxCacheId;
							}
							
						}
					));
			}
		}finally {
			// 释放“预申请锁”
			this.preApplyLock.unlock();
		}
	}
	/**
	 *	查询数据库（含重试）【与SequenceIdFactory不同】
	 * @return
	 */
	private long getNextMaxCacheIdFromDbRetry() {
		int times = 0 ;
		boolean trySuccess = false ;
		long maxCacheId = 0 ;
		while(times < this.tryTimes) {
			times ++ ;
			try {
				maxCacheId = this.getCurrentIdFromDb() + this.step ;
				trySuccess = true ;
				break ;
			}catch(Exception e) {
				try {
					// 休眠0.1s后重试
					Thread.sleep(100);
				} catch (InterruptedException ie) {
				}
				log.error("fatal error in TableIdFactory, tryTime:"+times);
			}
		}
		
		if(!trySuccess) {
			throw new RuntimeException("fatal error in TableIdFactory retry times run out, getNextMaxCacheIdFromDbRetry failed!!") ;
		}else{
			return maxCacheId ;
		}
	}
	
	/**
	 *	查询数据库【与SequenceIdFactory不同】
	 * @return	
	 */
	private long getCurrentIdFromDb(){
		// 最大值不会超过10的length次方
		long maxId = (long) Math.pow(10, length); 
		try {
			return this.tableJnlnoService.getNextIdFromDb(jnlType, maxId, step) ;
		}catch(Exception e) {
			log.error("fatal error in TableIdFactory! Exception: ", e) ;
			throw new RuntimeException("fatal error in TableIdFactory.getNextIdFromDb()") ;
		}
	}
	
	/**
     * The default thread factory
     */
    static class SequenceThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        SequenceThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "sequence-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    @Override
	public void afterPropertiesSet() throws Exception {
    	this.maxCacheId = this.getNextMaxCacheIdFromDbRetry() ;
    	this.currentId = this.maxCacheId - this.step ;
    	log.info("TableIdFactory init success, currentId:"+this.currentId+", maxCacheId:"+this.maxCacheId);
	}
    
	public void setJnlType(String jnlType) {
		this.jnlType = jnlType;
	}

	public void setCurrentId(long currentId) {
		this.currentId = currentId;
	}

	public void setMaxCacheId(long maxCacheId) {
		this.maxCacheId = maxCacheId;
	}

	public void setLoadFactor(float loadFactor) {
		this.loadFactor = loadFactor;
	}

	public void setStep(int step) {
		this.step = step;
	}

	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	public void setLength(int length) {
		this.length = length;
	}

	public void setTryTimes(int tryTimes) {
		this.tryTimes = tryTimes;
	}

	public void setGenerateTimeout(long generateTimeout) {
		this.generateTimeout = generateTimeout;
	}

	public void setPreApplytimeout(long preApplytimeout) {
		this.preApplytimeout = preApplytimeout;
	}

	public void setGenerateLock(Lock generateLock) {
		this.generateLock = generateLock;
	}

	public void setPreApplyLock(Lock preApplyLock) {
		this.preApplyLock = preApplyLock;
	}

	public void setPreApplyFuture(AtomicReference<Future<Long>> preApplyFuture) {
		this.preApplyFuture = preApplyFuture;
	}

	public void setPreApplyExecutor(ExecutorService preApplyExecutor) {
		this.preApplyExecutor = preApplyExecutor;
	}

	public void setTableJnlnoService(TableJnlnoService tableJnlnoService) {
		this.tableJnlnoService = tableJnlnoService;
	}
	
}
